package myutil.concurrency;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 *
 * @author Hlib_Babii
 */
public class Series2 {
    
    private final int n;
    private final int nThreads;

    private final int numbersPerProcess;
    private final int numbersLeft;

    public Series2(int n, int nThreads) {
        this.n = n;
        this.nThreads = nThreads;
        
        numbersPerProcess = (2 * n + 1) / nThreads;
        numbersLeft = (2 * n + 1) % nThreads;
    }
    
    private class ThreadTask implements Callable<Double> {
        
        private final int num;

        public ThreadTask(int num) {
            this.num = num;
        }
        
        @Override
        public Double call() throws Exception {
            return calcSubSum();
        }
        
        private double calcSubSum() {
            double thread_sum = 0.0; // sum of numbers counted by this thread
            for (int j = 0; j < numbersPerProcess; j++) {
                int arg = -n + num * numbersPerProcess + j;
                thread_sum += Math.sin(arg);
            }
            if (num < numbersLeft) {
                thread_sum += Math.sin(n - num);
            }
            return thread_sum;
        }
    }

    public double seriesSum(){

        double sum = 0.0;
        ExecutorService executorService = Executors.newCachedThreadPool();
        CompletionService<Double> completionService = new ExecutorCompletionService<>(executorService);
        for (int i = 0; i < nThreads; i++) {
            completionService.submit(new ThreadTask(i));
        }
        for (int i = 0; i < nThreads; i++) {
            double thread_sum = 0.0;
            try {
                Future<Double> thread_sum_future = completionService.take();
                thread_sum = thread_sum_future.get();
            } catch (InterruptedException | ExecutionException ex) {}
            sum += thread_sum;
        }
        executorService.shutdown();
        return sum;
    }

    public static void main(String[] args) {
        int failCounter = 0;
        Series2 s = new Series2(5000, 10);
        for (int i = 0; i < 500; i++) {
            double result = s.seriesSum();
            if (Math.abs(result) > 1e-6) {
                ++failCounter;
                System.out.println("wrong sum= " + result);
            }
            System.out.println("sum= " + result);
        }
        System.out.println("failCounter= " + failCounter);
    }
}
